﻿/**********************************************************************************************************************/
/* See License.txt file for details.                                                                                  */
/**********************************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

using CodePlex.SqlServer.LocalDB.API;
using System.Data.SqlClient;

namespace CodePlex.SqlServer.LocalDB.LocalDBCompanion
{
    internal static class InstanceSharingHelper
    {
        const string ConnectionStringTemplate = @"Server=(localdb)\{0};Database=master;Integrated Security=SSPI";
        const int MaintenanceInstanceStopTimeout = 4000;

        internal static void PingInstance(string instanceName)
        {
            using (var sqlConnection = new SqlConnection(string.Format(ConnectionStringTemplate, instanceName)))
            {
                sqlConnection.Open();

                // Any command will do, I just want to make sure the connection does somehing
                var pingCommand = new SqlCommand("select @@version", sqlConnection);
                int resultCount = pingCommand.ExecuteNonQuery();

                sqlConnection.Close();
            }
        }

        private static void ConfigureProcessInfo(ProcessStartInfo processInfo)
        {
            processInfo.Verb = "runas";
            processInfo.UseShellExecute = true;

            // Unfortunately this only works with UseShellExecute == false
            // But we need UseShellExecute == true for the elevation to work
            //processInfo.RedirectStandardError = true;
            //processInfo.RedirectStandardOutput = true;
        }

        internal static void ShareInstanceWithElevation(string privateInstanceName, string globalInstanceName)
        {
            //LocalDBAPI.ShareInstance(privateInstanceName, globalInstanceName);

            // If some (other) instance is already shared under the global name, unshare it first
            var sharedInstanceInfo = LocalDBAPI.GetInstanceInfo(".\\" + globalInstanceName);
            if (sharedInstanceInfo.DoesExist)
            {
                UnshareInstanceWithElevation(globalInstanceName);
            }

            var processInfo = new ProcessStartInfo("sqllocaldb.exe", "share " + privateInstanceName + " " + globalInstanceName);
            ConfigureProcessInfo(processInfo);

            var childProcess = new Process();
            childProcess.StartInfo = processInfo;
            childProcess.EnableRaisingEvents = true;

            childProcess.Start();
            childProcess.WaitForExit();

            // TODO: Figure out some way of detecting and presenting errors
            // This won't work without UseShellExecute == false
            // But when UseShellExecute == false then process elevation doesn't work at all
            //if (!childProcess.StandardError.EndOfStream)
            //{
            //    throw new Exception(childProcess.StandardError.ReadToEnd());
            //}

            if (childProcess.ExitCode != 0)
            {
                LocalDBAPI.ThrowExceptionForErrorCode((uint)childProcess.ExitCode);
            }
        }

        internal static void UnshareInstanceWithElevation(string globalInstanceName)
        {
            //LocalDBAPI.ShareInstance(privateInstanceName, globalInstanceName);

            var processInfo = new ProcessStartInfo("sqllocaldb.exe", "unshare .\\" + globalInstanceName);
            ConfigureProcessInfo(processInfo);

            var childProcess = new Process();
            childProcess.StartInfo = processInfo;
            childProcess.EnableRaisingEvents = true;

            childProcess.Start();
            childProcess.WaitForExit();

            // TODO: Figure out some way of detecting and presenting errors
            // This won't work without UseShellExecute == false
            // But when UseShellExecute == false then process elevation doesn't work at all
            //if (!childProcess.StandardError.EndOfStream)
            //{
            //    throw new Exception(childProcess.StandardError.ReadToEnd());
            //}

            if (childProcess.ExitCode != 0)
            {
                LocalDBAPI.ThrowExceptionForErrorCode((uint)childProcess.ExitCode);
            }
        }

        internal static void CreateAdminLogin(string privateInstanceName, string loginName)
        {
            LocalDBAPI.StopInstance(privateInstanceName, MaintenanceInstanceStopTimeout);
            LocalDBAPI.StartInstance(privateInstanceName);

            using (var sqlConnection = new SqlConnection(string.Format(ConnectionStringTemplate, privateInstanceName)))
            {
                sqlConnection.Open();

                // Let's check in case the login already exists
                var checkLoginCommand = new SqlCommand(
                    string.Format("select count(*) from sys.server_principals where name = \'{0}\'", loginName),
                    sqlConnection);
                int resultCount = (int)checkLoginCommand.ExecuteScalar();

                // No reults means the login doesn't exist yet, creating it now
                if (resultCount == 0)
                {
                    var createLoginCommand = new SqlCommand(
                        string.Format("create login [{0}] from windows", loginName),
                        sqlConnection);
                    resultCount = createLoginCommand.ExecuteNonQuery();
                }

                // Let's make sure the login has admin permissions to be able to create databases, etc.
                // TODO: Update to lowest needed permissions (create databases, own databases, what else?)
                var adminLoginCommand = new SqlCommand(
                    string.Format("exec sp_addsrvrolemember N\'{0}\', sysadmin", loginName),
                    sqlConnection);
                resultCount = adminLoginCommand.ExecuteNonQuery();

                sqlConnection.Close();
            }
        }

        internal static void DeleteAdminLogin(string privateInstanceName, string loginName)
        {
            LocalDBAPI.StopInstance(privateInstanceName, MaintenanceInstanceStopTimeout);
            LocalDBAPI.StartInstance(privateInstanceName);

            using (var sqlConnection = new SqlConnection(string.Format(ConnectionStringTemplate, privateInstanceName)))
            {
                sqlConnection.Open();

                // Let's check in case the login already exists
                var checkLoginCommand = new SqlCommand(
                    string.Format("select count(*) from sys.server_principals where name = \'{0}\'", loginName),
                    sqlConnection);
                int resultCount = (int)checkLoginCommand.ExecuteScalar();

                // Only try to delete the login if it actually exists :-)
                if (resultCount > 0)
                {
                    var dropLoginCommand = new SqlCommand(
                        string.Format("drop login [{0}]", loginName),
                        sqlConnection);
                    resultCount = dropLoginCommand.ExecuteNonQuery();
                }
                
                sqlConnection.Close();
            }
        }
    }
}
